using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SocketsXO_Server
{
    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client  socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        public StringBuilder sb = new StringBuilder();
    }

    public class AsynchronousSocketListener
    {
        #region mouse control parameters
        
        const int MOUSEEVENTF_LEFTDOWN = 0x2;
        const int MOUSEEVENTF_LEFTUP = 0x4;
        const int MOUSEEVENTF_MIDDLEDOWN = 0x20;
        const int MOUSEEVENTF_MIDDLEUP = 0x40;
        const int MOUSEEVENTF_MOVE = 0x1;
        const int MOUSEEVENTF_ABSOLUTE = 0x8000;
        const int MOUSEEVENTF_RIGHTDOWN = 0x8;
        const int MOUSEEVENTF_RIGHTUP = 0x10;

        //mouse location        
        public struct PONITAPI
        {
            public int x, y;
        }

        [DllImport("user32.dll")]
        public static extern int GetCursorPos(ref PONITAPI p);

        [DllImport("user32.dll")]
        public static extern int SetCursorPos(int x, int y);

        [DllImport("user32.dll")]
        public static extern int mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        //used to store the output of the last time, used by the LPF
        
        public static double[] last_Output = { 0, 0, 0 };
        

        #endregion        
       

        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);

        //Constructor
        public AsynchronousSocketListener()
        {
        }

        public static void StartListening()
        {
            // Establish the local endpoint for the socket.           
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 13001);

            // Create a TCP/IP socket.
            Socket listener = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the local endpoint and listen for incoming connections.
            try
            {
                listener.Bind(localEndPoint);
                listener.Listen(10);

                #region display current ip address of the server

                IPHostEntry IpEntry = Dns.GetHostEntry(Dns.GetHostName());
                //string myip = IpEntry.AddressList[0].ToString();
                string myip=string.Empty;

                foreach (IPAddress ip in IpEntry.AddressList)
                {
                    if (ip.AddressFamily == AddressFamily.InterNetwork)
                    {
                        myip = ip.ToString();
                        break;
                    }
                }

                Console.WriteLine("Waiting for a connection...");
                Console.WriteLine(myip);

                #endregion

                // Start an asynchronous socket to listen for connections. 
                while (true)
                {
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    listener.BeginAccept(
                        new AsyncCallback(AcceptCallback),
                        listener);                    

                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.
            allDone.Set();

            // Get the socket that handles the client request.
            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);

            // Create the state object.
            StateObject state = new StateObject();
            state.workSocket = handler;
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
        }

        public static void ReadCallback(IAsyncResult ar)
        {
            String content = String.Empty;

            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;

            // Read data from the client socket. 
            int bytesRead = handler.EndReceive(ar);

            if (bytesRead > 0)
            {
                // There  might be more data, so store the data received so far.
                state.sb.Append(Encoding.UTF8.GetString(
                    state.buffer, 0, bytesRead));

                // Check for end-of-file tag. If it is not there, read 
                // more data.
                content = state.sb.ToString();
                if (content.IndexOf("<XXF>") > -1)
                {

                    // Control the cursor
                    cursor_Control(handler, content);
                }
                else
                {
                    // Not all data received. Get more.
                    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);
                }
            }
        }

        private static void cursor_Control(Socket handler, String data)
        {
                        
            #region Get sensor data

            data = data.Replace("<XXF>", "").Trim("\0".ToCharArray());            
            string[] pairs = data.Split('|');
            double mYaw = double.Parse(pairs[0]);
            double mPitch = double.Parse(pairs[1]);
            double mRoll = double.Parse(pairs[2]);

            double accX = double.Parse(pairs[3]);
            double accY = double.Parse(pairs[4]);
            double accZ = double.Parse(pairs[5]);

            #endregion          

            #region Low-pass filter to smooth the sensor data
            /*

            //coefficient of the first-order low pass filter
            const double alpha = 0.08;

            //first order low-pass filter
            double out_Yaw = (1 - alpha) * last_Output[0] + alpha * mYaw;
            double out_Pitch = (1 - alpha) * last_Output[1] + alpha * mPitch;
            double out_Roll = (1 - alpha) * last_Output[2] + alpha * mRoll;

            //store the output for the LPF formula
            last_Output[0] = out_Yaw;
            last_Output[1] = out_Pitch;
            last_Output[2] = out_Roll;
             */

            #endregion

            #region Control the Cursor and Click

            PONITAPI p = new PONITAPI();            

            //get the location of the cursor
                GetCursorPos(ref p);

                int last_Location_X = p.x;
                int last_Location_Y = p.y;

            //don't let the cursor disappear
                int screen_Lim = 10;

            //Use a threshold to control the click
                if (Math.Abs(accZ) < 0.20)
                {
                    #region control cursor
                    //eliminate the interference between x-axis and y-axis
                    if ((Math.Abs(mRoll) > 5)&&(Math.Abs(mRoll) <=10))
                    {     
                        //micro adjust x+-
                        p.x = Math.Min(Screen.PrimaryScreen.Bounds.Width - screen_Lim, Math.Max(0, p.x + Math.Sign(mRoll)));
                    }
                    else if (Math.Abs(mRoll) > 10)
                    {
                        //macro adjust x+-
                        p.x = Math.Min(Screen.PrimaryScreen.Bounds.Width - screen_Lim, Math.Max(0, p.x + (int)mRoll));
                    }
                    else
                    {
                        
                        int delta_Y = 8;

                        if (mPitch > -15)
                        {
                            //macro adjust y- 
                            p.y = Math.Min(Screen.PrimaryScreen.Bounds.Height - screen_Lim, Math.Max(0, p.y - delta_Y));
                        }
                        else if ((mPitch >-17)&&(mPitch <=-15))
                        {
                            //micro adjust y-
                            p.y = Math.Min(Screen.PrimaryScreen.Bounds.Height - screen_Lim, Math.Max(0, p.y - 1));
                        }
                        else if ((mPitch < -19)&&(mPitch>=-21))
                        {
                            //micro adjust y+
                            p.y = Math.Min(Screen.PrimaryScreen.Bounds.Height - screen_Lim, Math.Max(0, p.y + 1));
                        }
                        else if (mPitch<-21)
                        {
                            //macro ajdst y+
                            p.y = Math.Min(Screen.PrimaryScreen.Bounds.Height - screen_Lim, Math.Max(0, p.y + delta_Y));
                        }

                    }
                    #endregion                   
                }
                else
                {
                    #region control click

                    mouse_event(MOUSEEVENTF_RIGHTDOWN, p.x, p.y, 0, 0);
                    Console.WriteLine("Pressed");
                    mouse_event(MOUSEEVENTF_RIGHTUP, p.x, p.y, 0, 0);
                    Thread.Sleep(10000);

                    #endregion
                }                

                #region set the location of the cursor

                double total_Delta = Math.Sqrt((p.x - last_Location_X) * (p.x - last_Location_X) + (p.y - last_Location_Y) * (p.y - last_Location_Y));

                //eliminate the jump
                if (total_Delta > 30)
                {                   
                    p.x = last_Location_X;
                    p.y = last_Location_Y;
                }
                //set the location of the cursor
                SetCursorPos(p.x, p.y);

                #endregion


            #endregion

                #region Display message in the console

                //Console.WriteLine("accX: {0} , accY: {1} , accZ: {2}",
                 //      accX,accY,accZ);

                Console.WriteLine("Yaw: {0} , Pitch: {1} , Roll: {2}",
                mYaw, mPitch, mRoll);          

                //Console.WriteLine("out_Yaw: {0} , out_Pitch: {1} , out_Roll: {2}",
                //out_Yaw, out_Pitch, out_Roll);

                //Console.WriteLine("first Yaw is: {0}",
                         //firstYaw);

                #endregion

            #region shutdown the socket

                handler.Shutdown(SocketShutdown.Both);            
                handler.Close();

            #endregion

        }

        public static void Main(String[] args)
        {
            StartListening();          
        }
    }
}
